/*
 * MIT License
 *
 * Copyright (c) 2024-2025 milkpotatoes
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#undef unix

#include <cstdint>
#include <iostream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

#define CXXOPTS_NO_EXCEPTIONS
#include "cxxopts.hpp"

#include "js_utils/js_process.h"
#include "napi_runner/arkruntime_utils.h"
#include "napi_runner/napi_runner.h"
#include "runner_utils/log.h"
#include "runner_utils/napi_utils.h"
#include "runner_utils/utils.h"

const cxxopts::Options GetArgParser(const char *name)
{
    cxxopts::Options parser(name, "Run ark byte code file with Node-API module in cmdline.");
    auto optAdder = parser.add_options();
    // clang-format off
    optAdder("h,help",        "Print the help message and exit.");
    optAdder("l,log-level",   "Log level of napi_runner.", cxxopts::value<std::string>()->default_value("error"));
    optAdder("j,jit",         "Enable jit for ArkJSRuntime.");
    optAdder("k,ark-options", "Options for ark js runtime.", cxxopts::value<std::vector<std::string>>());
    optAdder("file",          "Path of abc file and args passing to abc file.", cxxopts::value<std::string>());
    // clang-format on
    parser.parse_positional({"file"});
    parser.positional_help("file [args...]");
    return parser;
}

int GetAbcArgIndex(int argc, const char *argv[])
{
    const char *suf = ".abc";
    int index = 1;
    std::string arg;
    for (; index < argc; index++)
    {
        arg = argv[index];
        if (utils::EndsWith(arg, suf))
            return index;
    }
    return 0;
}

int ParseArgs(int argc, const char *argv[], int &abcIndex)
{
    cxxopts::Options parser = GetArgParser(argv[0]);
    if (argc < 2) // 2: at least have two arguments
    {
        std::cerr << parser.help() << std::endl;
        return 0;
    }

    abcIndex = GetAbcArgIndex(argc, argv);
    if (abcIndex <= 0)
    {
        std::cerr << "path of abc file must be provided.\n" << parser.help() << std::endl;
        return -1;
    }

    auto args = parser.parse(abcIndex, argv);
    if (args["jit"].as<bool>())
    {
        ArkRuntimeUtils::SetJITState(true);
    }

    // clang-format off
    std::unordered_map<std::string, RunnerLogLevel> logLevelMap{
        {"debug",   RunnerLogLevel::DEBUG},
        {"info",    RunnerLogLevel::INFO},
        {"warning", RunnerLogLevel::WARNING},
        {"error",   RunnerLogLevel::ERROR},
        {"fatal",   RunnerLogLevel::FATAL},
    };
    // clang-format on
    auto userLv = logLevelMap.find(args["log-level"].as<std::string>());
    auto runnerLv = userLv != logLevelMap.end() ? userLv->second : logLevelMap["error"];
    RunnerLogger::SetBaseLogLevel(runnerLv);
    NapiRunner::SetNApiLogLevel(runnerLv);

    auto arkOpts = args["ark-options"].as_optional<std::vector<std::string>>();
    if (arkOpts != std::nullopt)
    {
        auto arkArgs = arkOpts.value();
        std::vector<const char *> runtimeOpt;
        runtimeOpt.push_back(argv[0]);
        for (size_t i = 0; i < arkArgs.size(); i++)
        {
            if (std::string(arkArgs[i]) == "--help")
            {
                std::cout << "Usage: " << argv[0]
                          << " -k <option> ...\n"
                             "Options:\n"
                          << ArkRuntimeUtils::GetRuntimeHelpMessage() << std::endl;
                return 0;
            }
            runtimeOpt.push_back(arkArgs[i].c_str());
        }
        if (!ArkRuntimeUtils::ParseOptions(runtimeOpt.size(), runtimeOpt.data()))
        {
            std::cerr << "Failed to parse ark options.\n"
                         "use -k --help to check help message."
                      << std::endl;
            return -2;
        }
    }

    return 1;
}

int GetExitCode(int code)
{
    bool isSet = false;
    auto procCode = JsProcess::GetExitCode(isSet);
    return isSet ? procCode : code;
}

int main(int argc, const char *argv[])
{
    int index = 0;
    if (int code = ParseArgs(argc, argv, index); code <= 0)
    {
        return code;
    }

    ArkRuntimeUtils::Init();

    NapiRunner::InitProcessArgs(argc, argv, index);
    NapiRunner runner(ArkRuntimeUtils::GetRuntimeOptions());
    if (runner.InitLoop() != 0)
    {
        DEFAULT_LOGGER(ERROR) << "Faild to unref uv_handle.";
        return -3;
    }
    {
        utils::NapiHandleScope scope(runner.GetEnv());
        runner.InitNativeModule();
        runner.LoadTSModule();
        napi_call_function(runner.GetEnv(), utils::GetGlobal(runner.GetEnv()),
                           utils::GetProperty(runner.GetEnv(), utils::GetGlobal(runner.GetEnv()), "unregisterAll"), 0,
                           nullptr, nullptr);
    }

    if (runner.RunScript(std::string_view(argv[index])) != 0)
    {
        DEFAULT_LOGGER(ERROR) << "Exception occurd in execution.";
        return GetExitCode(1);
    }

    runner.RunLoop(UV_RUN_DEFAULT);
    return GetExitCode(0);
}
